package main

import (
	"bufio"
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	"github.com/shopspring/decimal"
	"github.com/spf13/cast"
	"github.com/xuri/excelize/v2"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
	"io"
	"os"
	"strings"
)

type Sph struct {
	Text     string `xml:",chardata"`
	Xh       string `xml:"Xh"`       //序号
	Qyspbm   string `xml:"Qyspbm"`   //企业商品编码（20字节）
	Spmc     string `xml:"Spmc"`     //商品名称，金额为负数时此行是折扣行，折扣行的商品名称应与上一行的商品名称一致（100字节）
	Ggxh     string `xml:"Ggxh"`     //规格型号（40字节）
	Jldw     string `xml:"Jldw"`     //计量单位（32字节）
	Spbm     string `xml:"Spbm"`     //商品编码(19字节) （必输项）
	Syyhzcbz string `xml:"Syyhzcbz"` //是否使用优惠政策标识0：不使用，1：使用（1字节）
	Lslbz    string `xml:"Lslbz"`    //零税率标识   空：非零税率，0：出口退税，1：免税，2：不征收，3普通零税率（1字节）
	Yhzcsm   string `xml:"Yhzcsm"`   //优惠政策说明（50字节）
	Sl       string `xml:"Sl"`       //数量
	Dj       string `xml:"Dj"`       //单价（中外合作油气田（原海洋石油）5%税率，单价为含税单价）
	Je       string `xml:"Je"`       //金额，当金额为负数时为折扣行
	Slv      string `xml:"Slv"`      //税率
	Se       string `xml:"Se"`       //税额
	Kce      string `xml:"Kce"`      //扣除额，用于差额税计算
}

type Spxx struct {
	Text string `xml:",chardata"`
	Sph  []*Sph `xml:"Sph"`
}

type Fp struct {
	Text    string `xml:",chardata"`
	Djh     string `xml:"Djh"`     //单据号（20字节）
	Gfmc    string `xml:"Gfmc"`    //购方名称（100字节）
	Gfsh    string `xml:"Gfsh"`    //购方税号
	Gfdzdh  string `xml:"Gfdzdh"`  //购方银行账号（100字节）
	Gfyhzh  string `xml:"Gfyhzh"`  //购方地址电话（100字节）
	Bz      string `xml:"Bz"`      //备注（240字节）
	Fhr     string `xml:"Fhr"`     //复核人（8字节）
	Skr     string `xml:"Skr"`     //收款人（8字节）
	Spbmbbh string `xml:"Spbmbbh"` //商品编码版本号(20字节) （必输项）
	Hsbz    string `xml:"Hsbz"`    //含税标志 0：不含税税率，1：含税税率，2：差额税;中外合作油气田（原海洋石油）5%税率、1.5%税率为1，差额税为2，其他为0；
	Spxx    Spxx   `xml:"Spxx"`
}

type Fpsj struct {
	Text string `xml:",chardata"`
	Fp   []*Fp  `xml:"Fp"`
}

type Fpxx struct {
	Text string `xml:",chardata"`
	Zsl  string `xml:"Zsl"`
	Fpsj Fpsj   `xml:"Fpsj"`
}

type Kp struct {
	XMLName xml.Name `xml:"Kp"`
	Text    string   `xml:",chardata"`
	Version string   `xml:"Version"`
	Fpxx    Fpxx     `xml:"Fpxx"`
}

// Company 客户
type Company struct {
	Name    string
	NO      string
	Address string
	Account string
}

func main() {
	fps, err := parseOrder()
	if err != nil {
		fmt.Println(err)
	}

	_ = os.Mkdir("结果", os.ModePerm)

	// 检验一下数据
	fmt.Println("序号\t单据号\t(单价*数量)合计\t合计\t是否匹配")

	at1 := decimal.NewFromFloat(0)
	at2 := decimal.NewFromFloat(0)

	for i, fp := range fps {
		t1 := decimal.NewFromFloat(0)
		t2 := decimal.NewFromFloat(0)
		for _, sph := range fp.Spxx.Sph {
			t1 = t1.Add(decimal.NewFromFloat(cast.ToFloat64(sph.Dj)).Mul(decimal.NewFromFloat(1.13)).Mul(decimal.NewFromFloat(cast.ToFloat64(sph.Sl))))
			t2 = t2.Add(decimal.NewFromFloat(cast.ToFloat64(cast.ToFloat64(sph.Je))).Mul(decimal.NewFromFloat(1.13)))
		}

		check := "是"
		if t1.Round(2).String() != t2.Round(2).String() {
			check = "否"
		}
		fmt.Println(fmt.Sprintf("%d\t%s\t%s\t%s\t%s", i+1, fp.Djh,
			t1.Round(2).String(), t2.Round(2).String(), check,
		))

		at1 = at1.Add(t1.Round(2))
		at2 = at2.Add(t2.Round(2))
	}

	fmt.Println(fmt.Sprintf("%s\t%s\t%s\t%s\t%s", "合计", "-",
		at1.Round(2).String(), at2.Round(2).String(), "-",
	))

	for _, fp := range fps {
		// 结构数据
		kp := &Kp{
			Version: "2.0",
			Fpxx: Fpxx{
				Zsl: "1",
				Fpsj: Fpsj{
					Text: "",
					Fp:   []*Fp{fp},
				},
			},
		}

		bb, _ := xml.MarshalIndent(kp, "", "  ")
		bb, _ = io.ReadAll(transform.NewReader(bytes.NewReader(bb), simplifiedchinese.GBK.NewEncoder()))
		if err = os.WriteFile("结果/"+fp.Djh+".xml", bb, os.ModePerm); err != nil {
			fmt.Println(err)
			break
		}
	}

	fmt.Println("处理完成。。。。")

	var a string
	_, _ = fmt.Scanln(&a)
}

// 解析销售单
func parseOrder() (fps []*Fp, err error) {
	companies, err := parseCompany()
	if err != nil {
		return
	}

	f, err := excelize.OpenFile("开票资料.xlsx")
	if err != nil {
		return
	}
	rows, err := f.Rows("开票清单")
	if err != nil {
		return
	}

	fp := new(Fp)
	sphMaps := make(map[string]*Sph)
	for i := 0; rows.Next(); i++ {
		if i == 0 { // 首行忽略
			continue
		}
		cols, _ := rows.Columns()
		if len(cols) == 0 || cols[0] == "" {
			continue
		}

		// 新的客户
		if fp.Djh != cols[0] && fp.Djh != "" {
			if fp.Bz != "" {
				fp.Bz = strings.Join(sliceStringUnion(strings.Split(fp.Bz, "、")), "、")
			}
			fps = append(fps, fp)
			fp = new(Fp)
			sphMaps = make(map[string]*Sph)
		}

		// 首次编码初始化客户信息
		if fp.Djh == "" {
			fp.Djh = cols[0]
			if company, has := companies[cols[1]]; !has {
				return nil, errors.New(cols[1] + " 客户未找到")
			} else {
				fp.Gfmc = company.Name
				fp.Gfsh = company.NO
				fp.Gfyhzh = company.Account
				fp.Gfdzdh = company.Address
				fp.Fhr = "彭丹"
				fp.Skr = "蔡术华"
				fp.Hsbz = "1"
				fp.Spbmbbh = "51"
				fp.Spxx = Spxx{}
			}
		}

		// 备注
		if strings.TrimSpace(cols[8]) != "" {
			fp.Bz = strings.Trim(fp.Bz+"、"+strings.TrimSpace(cols[8]), "、")
		}

		// 处理商品
		sph := &Sph{
			Xh:       fmt.Sprintf("%d", len(fp.Spxx.Sph)+1),
			Qyspbm:   "",
			Spmc:     cols[2],
			Ggxh:     cols[3],
			Jldw:     cols[4],
			Spbm:     addZero(cols[10]),
			Syyhzcbz: "",
			Lslbz:    "",
			Yhzcsm:   "",
			Sl:       strings.ReplaceAll(cols[5], ",", ""),
			Dj:       decimal.NewFromFloat(cast.ToFloat64(strings.ReplaceAll(cols[6], ",", ""))).Div(decimal.NewFromFloat(1.13)).String(),
			Je:       decimal.NewFromFloat(cast.ToFloat64(strings.ReplaceAll(cols[7], ",", ""))).Div(decimal.NewFromFloat(1.13)).String(),
			Slv:      "0.13",
			Se:       "", // 税额
			Kce:      "",
		}

		// 同一商品合并
		key := sph.Ggxh + "|" + sph.Spmc + "|" + sph.Dj
		if oldSph, ok := sphMaps[key]; ok {
			oldSph.Sl = decimal.NewFromFloat(cast.ToFloat64(sph.Sl)).Add(decimal.NewFromFloat(cast.ToFloat64(oldSph.Sl))).String()
			oldSph.Je = decimal.NewFromFloat(cast.ToFloat64(sph.Je)).Add(decimal.NewFromFloat(cast.ToFloat64(oldSph.Je))).String()
			continue
		} else {
			sphMaps[key] = sph
		}

		fp.Spxx.Sph = append(fp.Spxx.Sph, sph)
	}

	if fp.Djh != "" {
		fps = append(fps, fp)
	}

	return fps, nil
}

// 解析公司
func parseCompany() (map[string]Company, error) {
	// open the file
	file, err := os.Open("./客户编码.txt")
	if err != nil {
		return nil, err
	}
	fileScanner := bufio.NewScanner(file)
	companies := make(map[string]Company)
	// read line by line
	for fileScanner.Scan() {
		splits := strings.Split(fileScanner.Text(), "~~")
		if len(splits) <= 4 {
			continue
		}
		companies[splits[1]] = Company{
			Name:    splits[1],
			NO:      splits[3],
			Address: splits[4],
			Account: splits[5],
		}
	}

	return companies, nil
}

// 切片去重
func sliceStringUnion(ss []string) (rs []string) {
	exists := make(map[string]struct{})
	for _, s := range ss {
		if _, ok := exists[s]; ok {
			continue
		}
		rs = append(rs, s)
		exists[s] = struct{}{}
	}
	return
}

func addZero(str string) string {
	if len(str) >= 19 {
		return str
	}

	add := 19 - len(str)
	for add > 0 {
		str = str + "0"
		add--
	}

	return str
}
